package com.hj.springframework2.aop;

import com.hj.springframework2.context.ApplicationContextUtils;
import com.hj.springframework2.context.BeanDefinition;
import com.hj.springframework2.context.BeanPostProcessor;
import com.hj.springframework2.context.ConfigurableApplicationContext;
import com.hj.springframework2.exception.AopConfigException;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author : hujing
 * @Date: 2025/6/24 15:10
 * @Description:
 */
public abstract class AnnotationProxyBeanPostProcessor<A extends Annotation> implements BeanPostProcessor {
    
    Map<String,Object> originBeans = new HashMap<>();
    Class<A> annotationClass;
    
    public AnnotationProxyBeanPostProcessor(){
        this.annotationClass = getParameterizedType();
    }

    private Class<A> getParameterizedType() {
        Type type = getClass().getGenericSuperclass();
        if (!(type instanceof ParameterizedType)){
            throw new IllegalArgumentException("Superclass must be parameterized");
        }
        ParameterizedType pt = (ParameterizedType) type;
        Type[] types = pt.getActualTypeArguments();
        if (types.length != 1){
            throw new IllegalArgumentException("Superclass must have one parameter");
        }
        Type r = types[0];
        if (!(r instanceof Class<?>)){
            throw new IllegalArgumentException("Class " + getClass().getName() + " does not have parameterized type of class.");
        }
        return (Class<A>) r;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        Class<?> beanClass = bean.getClass();

        A anno = beanClass.getAnnotation(annotationClass);
        if (anno != null){
            String handlerName;
            try{
                handlerName = (String) anno.annotationType().getMethod("value").invoke(anno);
            }catch (Exception e){
                throw new AopConfigException(String.format("@%s must have value() returned String type.", this.annotationClass.getSimpleName()), e);
            }
            Object proxy = createProxy(beanClass,bean,handlerName);
            originBeans.put(beanName,bean);
            return proxy;
        }else{
            return bean;
        }
    }

    @Override
    public Object postProcessOnSetProperty(Object bean, String beanName) {
        Object origin = this.originBeans.get(beanName);
        return origin != null?origin:bean;
    }

    Object createProxy(Class<?> beanClass, Object bean, String handlerName){
        ConfigurableApplicationContext ctx = (ConfigurableApplicationContext)ApplicationContextUtils.getRequiredApplicationContext();

        BeanDefinition def = ctx.findBeanDefinition(handlerName);
        if (def == null){
            throw new AopConfigException(String.format("@%s proxy handler '%s' not found.", this.annotationClass.getSimpleName(), handlerName));
        }

        Object handlerBean = def.getInstance();
        if (handlerBean == null){
            handlerBean = ctx.createBeanAsEarlySingleton(def);
        }
        if (handlerBean instanceof InvocationHandler handler){
            return ProxyResolver.getInstance().createProxy(bean,handler);
        }else{
            throw new AopConfigException(String.format("@%s proxy handler '%s' is not type of %s.", this.annotationClass.getSimpleName(), handlerName,
                    InvocationHandler.class.getName()));
        }
    }

}
